Spring5源码解析-使用Spring AnnotationUtils处理注解
通过Java中的注解,程序员可以将配置文件中的一些配置通过使用Java类来实现。例如,在Spring中,通过@RequestMapping
注解,我们可以直接在controller
内配置URL映射
。一般来说,成功者的背后离不开一帮默默支持他的小伙伴,这里同样是,一旦离开其中一个,就甭指望实现所期望的功能,这里要说的一个就是AnnotationUtils
。
在本文中,我们将会看到AnnotationUtils类是如何给我们提供极大的便利的。首先,我们将关注下其所有可用的方法。进而,我们来看看这些方法用在了什么地方。最后,老规矩,Demo。
代码截的不少,主要还是为了在平时不一定有IDE环境下清楚的看清弄懂上下文的调用关系,也方面读者可以快速的找到相应的代码所在地。
另外,这一篇融合了前面很多篇的细节,有些不清晰明了的地方,请回头看前面的系列。
什么是Spring中的AnnotationUtils类?
AnnotationUtils
是一个专门用于处理复杂注解问题的类。其主要由公共和静态方法组成,它允许在类,方法或字段上检查注解。另外,AnnotationUtils
不仅仅来做简单的类分析。它也通过查找在超类和接口上的注解来做更多的事情。基于反射的API,AnnotationUtils
使用java.lang.reflect的 3个元素来处理注解:
- Annotation:表示注解。
- AnnotatedElement:表示被注解元素。
- Method:提供某些类或接口中的方法的信息。
现在,我们来看看AnnotationUtils
类中的最重要的几个public 方法:
- getAnnotation:有3个这样的名字的方法存在。 第一个接收参数
Annotation的对象实例
。第二个是AnnotatedElement
的实例。第三个getAnnotation
方法接收参数Method
对象。它们都从字段,类或方法得到并返回注解。
1 |
|
- getRepeatableAnnotations:通过向参数中传递
AnnotatedElement
和所要查找的注解类型
来访问,有两个使用此名称的方法。 这两个方法从所提供的AnnotatedElement
上得到可重复的注解(即传入的annotationType
)对应的元素。例如,它可以返回使用@RequestMapping
注解所注解的方法(请看下面的源码)。
1 | /** |
findAnnotation:通过传入
AnnotatedElement
和注解类型
来查找方法或者类对象上的注解。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59/**
* Find a single {@link Annotation} of {@code annotationType} on the
* supplied {@link AnnotatedElement}.
* <p>Meta-annotations will be searched if the annotation is not
* <em>directly present</em> on the supplied element.
* <p><strong>Warning</strong>: this method operates generically on
* annotated elements. In other words, this method does not execute
* specialized search algorithms for classes or methods. If you require
* the more specific semantics of {@link #findAnnotation(Class, Class)}
* or {@link #findAnnotation(Method, Class)}, invoke one of those methods
* instead.
* @param annotatedElement the {@code AnnotatedElement} on which to find the annotation
* @param annotationType the annotation type to look for, both locally and as a meta-annotation
* @return the first matching annotation, or {@code null} if not found
* @since 4.2
*/
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
Assert.notNull(annotatedElement, "AnnotatedElement must not be null");
// Do NOT store result in the findAnnotationCache since doing so could break
// findAnnotation(Class, Class) and findAnnotation(Method, Class).
A ann = findAnnotation(annotatedElement, annotationType, new HashSet<>());
return (ann != null ? synthesizeAnnotation(ann, annotatedElement) : null);
}
/**
* Perform the search algorithm for {@link #findAnnotation(AnnotatedElement, Class)}
* avoiding endless recursion by tracking which annotations have already
* been <em>visited</em>.
* @param annotatedElement the {@code AnnotatedElement} on which to find the annotation
* @param annotationType the annotation type to look for, both locally and as a meta-annotation
* @param visited the set of annotations that have already been visited
* @return the first matching annotation, or {@code null} if not found
* @since 4.2
*/
private static <A extends Annotation> A findAnnotation(
AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> visited) {
try {
A annotation = annotatedElement.getDeclaredAnnotation(annotationType);
if (annotation != null) {
return annotation;
}
for (Annotation declaredAnn : annotatedElement.getDeclaredAnnotations()) {
Class<? extends Annotation> declaredType = declaredAnn.annotationType();
if (!isInJavaLangAnnotationPackage(declaredType) && visited.add(declaredAnn)) {
annotation = findAnnotation((AnnotatedElement) declaredType, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
}
catch (Throwable ex) {
handleIntrospectionFailure(annotatedElement, ex);
}
return null;
}isAnnotationDeclaredLocally:检查注解是否在类中本地声明,而不是继承。
1 | /** |
- isAnnotationInherited:检查注解是否从另一个类继承(即未在本地声明)。
1 | /** |
- getAnnotationAttributes:获取给定注解的属性。
1 | /** |
- getValue:获取给定注解的值。 有这个名称的存在两种方法。第一个返回注解的全局值。第二个是指定注解参数的值。
1 | /** |
- getDefaultValue:获取给定注解或注解属性的默认值(注意
@Nullable
注解就知道为什么这么说了)。
1 | /** |
有哪些地方使用了AnnotationUtils方法?
很多Spring项目模块都用了AnnotationUtils
。这里我们将重点关注与core
和Web开发相关的项目模块:Web
,Web MVC
,context
和bean
。这里就不罗嗦太多了,只列出在这些Spring项目中使用的AnnotationUtils
的地方:
web MVC
AnnotationMethodHandlerAdapter
,直到 Spring 3.1的版本都是作为注解方法的主要处理程序,使用AnnotationUtils
来检查可用于方法级别的不同注解,如:@RequestMapping
,@ResponseStatus
,@ResponseBody
或@ModelAttribute
。- 作为
AnnotationMethodHandlerAdapter
接班人,RequestMappingHandlerMapping
与AnnotationUtils
一起解析@RequestMapping
并构造了封装映射配置(变量,HTTP方法, accepted headers 等)的RequestMappingInfo
对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/**
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo.
* @return the created RequestMappingInfo, or {@code null} if the method
* does not have a {@code @RequestMapping} annotation.
* @see #getCustomMethodCondition(Method)
* @see #getCustomTypeCondition(Class)
*/
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
}
return info;
}
/**
* Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
* supplying the appropriate custom {@link RequestCondition} depending on whether
* the supplied {@code annotatedElement} is a class or method.
* @see #getCustomTypeCondition(Class)
* @see #getCustomMethodCondition(Method)
*/
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}RequestMappingHandlerAdapter
是Web MVC
项目中使用AnnotationUtils
的第三个重要类。我们可以找到2个调用了findAnnotation()
方法并都返回MethodFilter
类的实例的方法。一个表示@InitBinder
注解,第一个表示@ModelAttribute
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62/**
* MethodFilter that matches {@link InitBinder @InitBinder} methods.
*/
public static final MethodFilter INIT_BINDER_METHODS = method ->
AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
/**
* MethodFilter that matches {@link ModelAttribute @ModelAttribute} methods.
*/
public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
((AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) &&
(AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null));
//以上两个方法的用法
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
if (logger.isInfoEnabled()) {
logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
}
List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(beans);
List<Object> requestResponseBodyAdviceBeans = new ArrayList<Object>();
for (ControllerAdviceBean bean : beans) {
//传入MODEL_ATTRIBUTE_METHODS这个表达式
Set<Method> attrMethods = MethodIntrospector.selectMethods(bean.getBeanType(), MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(bean, attrMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @ModelAttribute methods in " + bean);
}
}
//传入 INIT_BINDER_METHODS 这个表达式
Set<Method> binderMethods = MethodIntrospector.selectMethods(bean.getBeanType(), INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(bean, binderMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @InitBinder methods in " + bean);
}
}
if (RequestBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
requestResponseBodyAdviceBeans.add(bean);
if (logger.isInfoEnabled()) {
logger.info("Detected RequestBodyAdvice bean in " + bean);
}
}
if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
requestResponseBodyAdviceBeans.add(bean);
if (logger.isInfoEnabled()) {
logger.info("Detected ResponseBodyAdvice bean in " + bean);
}
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
}
接上面最后一个方法selectMethods
的实现,然后,我们平时写代码的时候也可以参考此实现形式:
org.springframework.core.MethodIntrospector
1 | /** |
web(此处分析俩过时的类,Spring5里没有,但是4里面包含有,之前的文章有写)
- 如果对象必须使用
@Valid
进行验证,或者@InitBinder
方法存在,HandlerMethodInvoker
使用AnnotationUtils
来知道@ModelAttribute
注解是什么。 - 这个项目的另一个关键类,
HandlerMethodResolver
,通过调用AnnotationUtils
方法来确定方法的类型(handler,binder或model-attribute)。具体点讲就是通过3种方法完成:isHandlerMethod
,isInitBinderMethod
和isModelAttributeMethod
。每个都接受Method的实例为参数。
- 如果对象必须使用
context
- 用于解析bean注解的类
BeanAnnotationHelper
使用AnnotationUtils
中的findMergedAnnotation()
方法来处理用@Bean
注解的类。我们使用它来确定bean的名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/**
* Utilities for processing {@link Bean}-annotated methods.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
*/
class BeanAnnotationHelper {
public static boolean isBeanAnnotated(Method method) {
return AnnotatedElementUtils.hasAnnotation(method, Bean.class);
}
public static String determineBeanNameFor(Method beanMethod) {
// By default, the bean name is the name of the @Bean-annotated method
String beanName = beanMethod.getName();
// Check to see if the user has explicitly set a custom bean name...
Bean bean = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Bean.class);
if (bean != null && bean.name().length > 0) {
beanName = bean.name()[0];
}
return beanName;
}
}
- 还有一个要说的就是
AnnotationAsyncExecutionInterceptor
类,其内同样使用AnnotationUtils
来解析注解。它调用findMergedAnnotation()
方法来解析在运行时所执行的方法的名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60/**
* Specialization of {@link AsyncExecutionInterceptor} that delegates method execution to
* an {@code Executor} based on the {@link Async} annotation. Specifically designed to
* support use of {@link Async#value()} executor qualification mechanism introduced in
* Spring 3.1.2. Supports detecting qualifier metadata via {@code @Async} at the method or
* declaring class level. See {@link #getExecutorQualifier(Method)} for details.
*
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.1.2
* @see org.springframework.scheduling.annotation.Async
* @see org.springframework.scheduling.annotation.AsyncAnnotationAdvisor
*/
public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionInterceptor {
/**
* Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor
* and a simple {@link AsyncUncaughtExceptionHandler}.
* @param defaultExecutor the executor to be used by default if no more specific
* executor has been qualified at the method level using {@link Async#value()};
* as of 4.2.6, a local executor for this interceptor will be built otherwise
*/
public AnnotationAsyncExecutionInterceptor(@Nullable Executor defaultExecutor) {
super(defaultExecutor);
}
/**
* Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor.
* @param defaultExecutor the executor to be used by default if no more specific
* executor has been qualified at the method level using {@link Async#value()};
* as of 4.2.6, a local executor for this interceptor will be built otherwise
* @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to
* handle exceptions thrown by asynchronous method executions with {@code void}
* return type
*/
public AnnotationAsyncExecutionInterceptor(@Nullable Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) {
super(defaultExecutor, exceptionHandler);
}
/**
* Return the qualifier or bean name of the executor to be used when executing the
* given method, specified via {@link Async#value} at the method or declaring
* class level. If {@code @Async} is specified at both the method and class level, the
* method's {@code #value} takes precedence (even if empty string, indicating that
* the default executor should be used preferentially).
* @param method the method to inspect for executor qualifier metadata
* @return the qualifier if specified, otherwise empty string indicating that the
* {@linkplain #setExecutor(Executor) default executor} should be used
* @see #determineAsyncExecutor(Method)
*/
protected String getExecutorQualifier(Method method) {
// Maintainer's note: changes made here should also be made in
// AnnotationAsyncExecutionAspect#getExecutorQualifier
Async async = AnnotatedElementUtils.findMergedAnnotation(method, Async.class);
if (async == null) {
async = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Async.class);
}
return (async != null ? async.value() : null);
}
}- 用于解析bean注解的类
bean
- 我们可以在
StaticListableBeanFactory
或DefaultListableBeanFactory
类中找到AnnotationUtils
用来查找bean
的注解的用法。
org.springframework.beans.factory.support.StaticListableBeanFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType)
throws BeansException {
Map<String, Object> results = new LinkedHashMap<>();
for (String beanName : this.beans.keySet()) {
if (findAnnotationOnBean(beanName, annotationType) != null) {
results.put(beanName, getBean(beanName));
}
}
return results;
}
public <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
throws NoSuchBeanDefinitionException{
Class<?> beanType = getType(beanName);
return (beanType != null ? AnnotationUtils.findAnnotation(beanType, annotationType) : null);
}org.springframework.beans.factory.support.DefaultListableBeanFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType) {
List<String> results = new ArrayList<>();
for (String beanName : this.beanDefinitionNames) {
BeanDefinition beanDefinition = getBeanDefinition(beanName);
if (!beanDefinition.isAbstract() && findAnnotationOnBean(beanName, annotationType) != null) {
results.add(beanName);
}
}
for (String beanName : this.manualSingletonNames) {
if (!results.contains(beanName) && findAnnotationOnBean(beanName, annotationType) != null) {
results.add(beanName);
}
}
return results.toArray(new String[results.size()]);
}
public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
String[] beanNames = getBeanNamesForAnnotation(annotationType);
Map<String, Object> results = new LinkedHashMap<>(beanNames.length);
for (String beanName : beanNames) {
results.put(beanName, getBean(beanName));
}
return results;
}
/**
* Find a {@link Annotation} of {@code annotationType} on the specified
* bean, traversing its interfaces and super classes if no annotation can be
* found on the given class itself, as well as checking its raw bean class
* if not found on the exposed bean reference (e.g. in case of a proxy).
*/
public <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
throws NoSuchBeanDefinitionException{
A ann = null;
Class<?> beanType = getType(beanName);
if (beanType != null) {
ann = AnnotationUtils.findAnnotation(beanType, annotationType);
}
if (ann == null && containsBeanDefinition(beanName)) {
BeanDefinition bd = getMergedBeanDefinition(beanName);
if (bd instanceof AbstractBeanDefinition) {
AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
if (abd.hasBeanClass()) {
ann = AnnotationUtils.findAnnotation(abd.getBeanClass(), annotationType);
}
}
}
return ann;
}
- 我们可以在
AnnotationUtils in Action
为了更好地理解Spring中AnnotationUtils的工作方式,我们来搞两个注解:第一个对应方法,第二个对应类。之后,写两个测试类和一个playground
类,我们要达到的目的是输出由AnnotationUtils
完成的注解分析结果。这两个注解定义如下:
1 | (RetentionPolicy.RUNTIME) |
1 | (RetentionPolicy.RUNTIME) |
@Retention注解是必需的(估计这里是大家的知识盲区,所以特地点出来)。否则AnnotationUtils将无法检测到这些注解。注意在StaticTextAnnotation
中存在value()
属性,并且在ClassNameAnnotation
中不存在此属性。在以下代码中,你可以找到相应的测试类:
1 | "TestChildren") (className = |
1 | public class TestChildren extends TestParent { |
TestChildren
类没有任何注解。我们使用它来测试继承注解检查。
1 | public class Playground { |
Playground
的main
方法结果如下:
1 | of method is: .migo.annotations.StaticTextAnnotation(text=Test text, value=Custom text value) |
如上所示,我们可以很轻易的了解很多注解点。我们可以检查value()
属性或另一个自定义属性的值。我们还可以检查属性的默认值。除此之外,AnnotationUtils
还可以在继承体系结构中进行注解操作。